﻿// ------------------------------------------------------------------------------
// <copyright file="ChestBard.cs" company="MyCompany.com">
//  Copyright © MyCompany.com. All rights reserved.
// </copyright>
// <author>Me</author>
// ------------------------------------------------------------------------------

namespace BTerrariaMod.Bards
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Terraria;

    /// <summary>
    /// Annouces when items of interest are looted from a chest.
    /// </summary>
    public class ChestBard
    {
        #region Constants

        /// <summary>
        /// Provides a default minimum value for interesting items.
        /// </summary>
        public const int DefaultValueThreshold = 8000;

        #endregion

        #region Private members

        /// <summary>
        /// TODO: What are we using this for?
        /// </summary>
        private readonly Action<string> output;

        /// <summary>
        /// TODO: What are we using this for?
        /// </summary>
        private Action<string> diagnostics;

        /// <summary>
        /// This stores all of the openend chests.
        /// </summary>
        private IDictionary<int, IEnumerable<Item>> openedChests = new Dictionary<int, IEnumerable<Item>>();

        /// <summary>
        /// TODO: What are we using this for?
        /// </summary>
        private Func<Item, bool> announceCriteriaBehavior;

        /// <summary>
        /// Backing member for the value threshold.
        /// </summary>
        private int valueThreshold = DefaultValueThreshold;

        #endregion

        #region Constructor(s)

        /// <summary>
        /// Initializes a new instance of the <see cref="ChestBard"/> class.
        /// </summary>
        /// <param name="output">The output.</param>
        public ChestBard(Action<string> output)
        {
            this.output = output;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the diagnostics.
        /// </summary>
        /// <value>
        /// The diagnostics.
        /// </value>
        public Action<string> Diagnostics
        {
            get
            {
                return this.diagnostics;
            }

            set
            {
                this.diagnostics = value;
            }
        }

        /// <summary>
        /// Gets or sets the value threshold.
        /// </summary>
        /// <value>
        /// The value threshold.
        /// </value>
        public int ValueThreshold
        {
            get
            {
                return this.valueThreshold;
            }

            set
            {
                this.valueThreshold = value;
            }
        }

        /// <summary>
        /// Gets or sets the announce criteria behavior.
        /// </summary>
        /// <value>
        /// The announce criteria behavior.
        /// </value>
        public Func<Item, bool> AnnounceCriteriaBehavior
        {
            get
            {
                return this.announceCriteriaBehavior ?? this.IsChestLootOfInterest;
            }

            set
            {
                this.announceCriteriaBehavior = value;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Runs when a chest is opened.
        /// </summary>
        /// <param name="activePlayer">The active player.</param>
        /// <param name="openedChest">The opened chest.</param>
        public void ChestOpened(Player activePlayer, Chest openedChest)
        {
            Contract.Assert(activePlayer.chest >= 0);
            Contract.Assert(openedChest != null && openedChest.item != null);
            if (!this.openedChests.ContainsKey(activePlayer.chest))
            {
                this.Log("open chest at " + openedChest.x.ToString() + "," + openedChest.y);
                this.openedChests.Add(activePlayer.chest, openedChest.item.ToList());
                foreach (var i in openedChest.item)
                {
                    if (this.IsChestLootOfInterest(i))
                    {
                        this.AnnounceChestItems(activePlayer, openedChest, i);
                    }
                    else
                    {
                        if (i != null && string.IsNullOrEmpty(i.name) == false)
                        {
                            this.Log("excluded item:" + i.name);
                        }
                    }
                }
            }

            this.UpdateChestCache(activePlayer.chest, openedChest);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Logs the specified diagnostic.
        /// </summary>
        /// <param name="diagnostic">The diagnostic.</param>
        private void Log(string diagnostic)
        {
            if (this.diagnostics != null)
            {
                this.diagnostics(diagnostic);
            }
        }

        /// <summary>
        /// Updates the chest cache.
        /// </summary>
        /// <param name="chestID">The chest ID.</param>
        /// <param name="chest">The chest.</param>
        private void UpdateChestCache(int chestID, Chest chest)
        {
            if (chest != null)
            {
                this.openedChests[chestID] = chest.item;
            }
            else
            {
                this.openedChests[chestID] = null;
            }
        }

        /// <summary>
        /// Announces the chest items.
        /// </summary>
        /// <param name="activePlayer">The active player.</param>
        /// <param name="freshChest">The fresh chest.</param>
        /// <param name="i">The i.</param>
        private void AnnounceChestItems(Player activePlayer, Chest freshChest, Item i)
        {
            var text = activePlayer.name + " has found ";
            if (i.maxStack > 1 && i.stack > 1)
            {
                text += i.stack + " " + i.name + "s";
            }
            else
            {
                text += i.name;
            }

            text += " in " + activePlayer.chest;

            this.Log(text);
            this.output(text);
        }

        /// <summary>
        /// Determines whether the chest loot is of interest.
        /// </summary>
        /// <param name="i">The item.</param>
        /// <returns>
        ///   <c>true</c> if the chest loot is of interest; otherwise, <c>false</c>.
        /// </returns>
        private bool IsChestLootOfInterest(Item i)
        {
            return i != null && (i.accessory || i.value > this.ValueThreshold);
        }

        #endregion
    }
}
